package com.tc.data.preheat;

import com.tc.preheat.PreheatApi;
import com.tc.data.datasource.ReadWriteDataSource;
import com.tc.data.datasource.RepositoryShardingDataSource;
import com.tc.data.spring.util.DatasourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

/**
 * <ul>
 * <li>预热原因：spring加载data的配置，并且对数据库连接池进行初始化，导致在第一个连接的时候会去建立出事连接，比较耗时。为了避免重启后的毛刺，需要对数据库连接池进行预热。
 * <li>预热方法：获取到所有的数据源，然后调用一次connection 及执行一次select 1 from dual ,即可进行预热.
 * <li>预热包含的场景：单个库的预热，拆库的预热，读写分离的预热.
 * <li>测试方法：启动后，通过jconsole等查看jmx,查看数据源对应的数据库连接池是否创建成功。
 * <li>注意点：如果数据源特别多，则该处预热会花费较长时间。
 * </ul>
 * 
 * @author freeman.he
 *
 */
public class DatabasePreheatListener implements PreheatApi, ApplicationContextAware {

	private static final Logger LOGGER = LoggerFactory.getLogger(DatabasePreheatListener.class);

	private ApplicationContext applicationContext;

	private String matrixName;

	@Override
	public List<String> preheatClass() {
		return null;
	}

	//其中RepositoryShardingDataSource是对外暴露的datasource,RepositoryShardingDataSource包含只有一个写(数据库连接池的datasource)或者读写分离(ReadWriteDataSource)
	//其中ReadWriteDataSource里面包含该group的所有数据库连接池.
	//从上往下遍历所有的数据库连接池，获取到连接并且执行一条语句即可。
	@Override
	public void preheatInfrastrcuture() {
		//获取到对外暴露的datasource
		Map<String, RepositoryShardingDataSource> repositoryShardingDataSources = applicationContext
				.getBeansOfType(RepositoryShardingDataSource.class);
		if (repositoryShardingDataSources != null && repositoryShardingDataSources.size() > 0) {
			for (RepositoryShardingDataSource repositoryShardingDataSource : repositoryShardingDataSources.values()) {
				try {
					Map<Object, Object> writeMap = repositoryShardingDataSource.getPrehotWriteDataSource();
					if(writeMap != null && writeMap.size() >0){
						for(Object object : writeMap.values()){
							processWriteDataSource(object,repositoryShardingDataSource);
						}
					}
					
				} catch (SQLException e) {
					LOGGER.warn(matrixName + " validateConnection error :" + e.getMessage());
				}
			}
		}
	}
	//对拆库便利
	public void processWriteDataSource(Object object, RepositoryShardingDataSource repositoryShardingDataSource) throws SQLException{
		DataSource dataSource = repositoryShardingDataSource.resolveSpecifiedDataSource(object);
		if(dataSource instanceof ReadWriteDataSource){
			processReadDataSource((ReadWriteDataSource) dataSource);
		}else{
			validateConnection(dataSource);
		}
	}
	//读写分离便利
	public void processReadDataSource(ReadWriteDataSource readWriteDataSource ) throws SQLException{
		Map<Object, Object> writeMap = readWriteDataSource.getPrehotWriteDataSource();
		if(writeMap != null && writeMap.size() >0){
			for(Object object : writeMap.values()){
				DataSource dataSource = readWriteDataSource.resolveSpecifiedDataSource(object);
				validateConnection(dataSource);
			}
		}
	}
	private void validateConnection(DataSource dataSource) {
		try {
			DatasourceUtils.validateConnection(dataSource);
		} catch (Exception e) {
			LOGGER.warn(matrixName + " validateConnection error :" + e.getMessage());
		}
	}

	@Override
	public void preheatApp() {

	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	public String getMatrixName() {
		return matrixName;
	}

	public void setMatrixName(String matrixName) {
		this.matrixName = matrixName;
	}

}
